package com.zhangtong.excel;

import com.zhangtong.excel.entity.StatisticsData;
import com.zhangtong.excel.entity.Title;
import com.zhangtong.excel.filter.IFilter;
import com.zhangtong.excel.generator.*;
import com.zhangtong.excel.tool.ExcelTool;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.nio.file.FileAlreadyExistsException;
import java.util.*;

public class StatisticsGenerator {

    static Map<StatisticsStyle, IGenerator> generators;

    static {
        generators = new HashMap<>();
        generators.put(StatisticsStyle.StaffAndProject, new StaffProjectGenerator());
        generators.put(StatisticsStyle.ProjectAndStaff, new ProjectStaffGenerator());
        generators.put(StatisticsStyle.Project, new ProjectGenerator());
        generators.put(StatisticsStyle.Staff, new StaffGenerator());
    }

    private Title title;
    private boolean hasTitle = true;
    private XSSFWorkbook excel;
    private File excelFile;
    private XSSFSheet readingSheet;
    private StatisticsData statistics;
    List<IFilter> filters;
    private int projectColumn = 'B';
    private int nameColumn = 'F';
    private int worktimeColumn = 'J';


    public StatisticsGenerator() {
        this.filters = new LinkedList<>();
    }

    public StatisticsGenerator setProjectColumn(char column) {
        this.projectColumn = Character.toUpperCase(column);
        return this;
    }

    public StatisticsGenerator setNameColumn(char column) {
        this.nameColumn = Character.toUpperCase(column);
        return this;
    }

    public StatisticsGenerator setWorktimeColumn(char column) {
        this.worktimeColumn = Character.toUpperCase(column);
        return this;
    }

    public StatisticsGenerator loadExcel(String excelFilename) throws IOException, InvalidFormatException {
        this.excelFile = new File(excelFilename);
        excel = new XSSFWorkbook(excelFile);
        System.out.println("加载Excel文件： " + excelFile.getAbsolutePath());
        return this;
    }

    public StatisticsGenerator openSheet(String sheetName) {
        if(excel.getNumberOfSheets() == 1){
            sheetName = excel.getSheetName(0);
        }
        readingSheet = excel.getSheet(sheetName);
        hasTitle = true;
        System.out.println("打开Sheet： " + sheetName);
        return this;
    }

    public StatisticsGenerator addFilter(IFilter filter) {
        this.filters.add(filter);
        return this;
    }

    public StatisticsGenerator addFilters(IFilter... filters) {
        for (IFilter filter : filters) {
            this.filters.add(filter);
        }
        return this;
    }


    public StatisticsGenerator doStatistic() {
        System.out.println("开始统计数据...");
        Iterator<Row> rows = readingSheet.rowIterator();

        statistics = new StatisticsData();
        if (hasTitle) {
            rows.next();
        }

        while (rows.hasNext()) {
            Row row = rows.next();
            addWorktime(statistics, row);
        }

        System.out.println("完成数据统计");
        System.out.println();
        return this;
    }

    /**
     * 设置读取的sheet页是否有标题行，默认为"有"
     *
     * @param hasTitle
     * @return
     */
    public StatisticsGenerator hasTitle(boolean hasTitle) {
        this.hasTitle = hasTitle;
        return this;
    }

    /**
     * 设置统计结果表中的标题
     *
     * @param title
     * @return
     */
    public StatisticsGenerator setResultTitles(Title title) {
        this.title = title;
        return this;
    }

    public StatisticsGenerator saveStatistics(String filename, boolean overwrite) throws IOException {
        StatisticsStyle statisticsStyle = StatisticsStyle.guessStatisticsStyle(title);
        System.out.println("生成统计结果(" + statisticsStyle + ")...");

        checkFile(filename, overwrite);
        XSSFWorkbook workbook = generateWorkbook(generators.get(statisticsStyle));
        saveToFile(workbook, filename);
        System.out.println("统计结果保存成功：" + filename);
        System.out.println();

        return this;
    }

    public StatisticsGenerator smartSaveStatistics(boolean overwrite) throws IOException {
        return saveStatistics(smartFilename(overwrite), overwrite);
    }

    public StatisticsGenerator smartSaveStatistics() throws IOException {
        return smartSaveStatistics(false);
    }

    public void close() throws IOException {
        excel.close();
    }

    private String smartFilename(boolean overwrite) {
        String ext = ".xlsx";
        StatisticsStyle statisticsStyle = StatisticsStyle.guessStatisticsStyle(title);
        String filename = excelFile.getName().substring(0, excelFile.getName().lastIndexOf('.')) + "(" + statisticsStyle + ")";
        File file = new File(excelFile.getParentFile(), filename + ext);

        if (overwrite) {
            return file.getAbsolutePath();
        }

        if (file.exists()) {
            for (int i = 0; i < 100; i++) {
                file = new File(excelFile.getParentFile(), filename + "-" + i + ext);
                if (!file.exists()) {
                    break;
                }
            }
        }
        return file.getAbsolutePath();
    }

    private void checkFile(String filename, boolean overwrite) throws FileAlreadyExistsException {
        File excelFile = new File(filename);
        if (excelFile.exists()) {
            if (overwrite) {
                excelFile.delete();
            } else {
                throw new FileAlreadyExistsException("文件已存在，不可覆盖");
            }
        }
    }

    private void addWorktime(StatisticsData statistics, Row row) {
        String project = getProject(row);
        String name = getName(row);
        BigDecimal worktime = getWorktime(row);
        project = doFilters(project);
        name = doFilters(name);

        statistics.addWorktime(name, project, worktime);
    }

    private String doFilters(String data) {
        String result = data;
        for (IFilter filter : filters) {
            result = filter.filter(result);
        }
        return result;
    }

    public String getProject(Row row) {
        return row.getCell(this.projectColumn - 'A').getStringCellValue();
    }

    public String getName(Row row) {
        return row.getCell(this.nameColumn - 'A').getStringCellValue();
    }

    public BigDecimal getWorktime(Row row) {
        double val = row.getCell(this.worktimeColumn - 'A').getNumericCellValue();
        return BigDecimal.valueOf(val);
    }

    private XSSFWorkbook generateWorkbook(IGenerator generator) {
        ExcelContext context = new ExcelContext();
        context.currentRowIdx = 0;
        context.workbook = new XSSFWorkbook();
        context.currentSheet = context.workbook.createSheet(readingSheet.getSheetName()+"-统计结果");
        context.cellStyle = ExcelTool.createDefaultCellStyle(context.workbook);
        context.titleStyle = ExcelTool.createTitleStyle(context.workbook);

        createTitle(context);
        context.setCurrentDataRowIdx(0);
        generator.generateRows(statistics, title, context);
        ExcelTool.fitColumns(context.currentSheet, title);

        return context.workbook;
    }


    private static void saveToFile(XSSFWorkbook workbook, String filename) throws IOException {
        OutputStream out = new FileOutputStream(filename);
        workbook.write(out);
        workbook.close();
        out.close();
    }

    public void createTitle(ExcelContext context) {
        Row row = context.createNextRow();

        Cell sequenceCell = ExcelTool.createCell(row, 0, context.titleStyle);
        sequenceCell.setCellValue("序号");
        int idx = 1;
        for (Map.Entry<String, String> entry : title.entrySet()) {
            Cell cell = ExcelTool.createCell(row, idx, context.titleStyle);
            cell.setCellValue(entry.getValue());
            idx++;
        }
    }

    enum StatisticsStyle {
        StaffAndProject("按员工-项目"),
        ProjectAndStaff("按项目-员工"),
        Staff("仅按员工"),
        Project("仅按项目");

        protected String desc;

        StatisticsStyle(String desc) {
            this.desc = desc;
        }

        @Override
        public String toString() {
            return this.desc;
        }

        public static StatisticsStyle guessStatisticsStyle(Title title) {
            List<String> list = new LinkedList<>();
            for (String s : title.keySet()) {
                list.add(s);
            }
            if (list.size() == 2) {
                if (list.get(0).equals(Constant.PROJECT)) {
                    return StatisticsStyle.Project;
                } else if (list.get(0).equals(Constant.NAME)) {
                    return StatisticsStyle.Staff;
                } else {
                    return null;
                }
            } else if (list.size() == 3) {
                String first = list.get(0);
                String second = list.get(1);
                if (first.equals(Constant.PROJECT) && second.equals(Constant.NAME)) {
                    return StatisticsStyle.ProjectAndStaff;
                } else if (first.equals(Constant.NAME) && second.equals(Constant.PROJECT)) {
                    return StatisticsStyle.StaffAndProject;
                }
            }
            return null;
        }
    }

    public StatisticsGenerator byStaffAndProject() {
        Title title = new Title();
        title.put(Constant.NAME, "姓名");
        title.put(Constant.PROJECT, "项目");
        title.put(Constant.WORKTIME, "工时（小时）");
        this.title = title;
        return this;
    }

    public StatisticsGenerator byProjectAndStaff() {
        Title title = new Title();
        title.put(Constant.PROJECT, "项目");
        title.put(Constant.NAME, "姓名");
        title.put(Constant.WORKTIME, "工时（小时）");
        this.title = title;
        return this;
    }

    public StatisticsGenerator byStaff() {
        Title title = new Title();
                title.put(Constant.NAME, "姓名");
        title.put(Constant.WORKTIME, "工时（小时）");
        this.title = title;
        return this;
    }

    public StatisticsGenerator byProject() {
        Title title = new Title();
        title.put(Constant.PROJECT, "项目");
        title.put(Constant.WORKTIME, "工时（小时）");
        this.title = title;
        return this;
    }
}
